Skip to content

UN-4286 [FEAT] Add the v1 plugin manifest, slots and build check - #2394

Open
athul-rs wants to merge 29 commits into
mainfrom
UN-4286-plugin-manifest
Open

athul-rs wants to merge 29 commits into
mainfrom
UN-4286-plugin-manifest

Conversation

@athul-rs

@athul-rs athul-rs commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What

  • Plugin manifest, slots and registry for Frontend v1 (P0-T07): a plugin declares the routes and slot fillers it adds in plugins/<id>/manifest.ts; shell and pages render <Slot id="…"> from @v1/lib/plugins.
  • plugins/index.ts discovers manifests with import.meta.glob, keeps those for the current VITE_DISTRIBUTION, and builds the registry. PluginProvider hands the slot renderer to lib/ through context, so lib/ keeps importing only lib/.
  • A build check (scripts/v1-plugins.mjs) fails the build when two plugins fill a single-filler slot or claim the same route.
  • .gitignore keeps overlaid cloud plugins out of OSS commits; the frontend Docker build passes VITE_DISTRIBUTION through.

Why

How

  • A slot with no filler renders its fallback, so OSS shows nothing where a cloud plugin would sit. A filler that throws is caught per slot and reported, and the rest of the page renders.
  • skipLibCheck in src/v1/tsconfig.json now comes from the scaffold (UN-4270 [FEAT] v1 scaffold, strict TypeScript and import boundary #2380, 664d1cf), which settled the @types/chai / vitest containSubset clash. This branch merged the scaffold and takes its tsconfig, so the earlier temporary commit no longer changes anything. The scaffold's new rule against dynamic imports it cannot analyse is met by missing-import.test.ts (a template whose fixed prefix names the folder).

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. Everything is under frontend/src/v1/, which no v0 route renders. Outside it: a .gitignore rule for frontend/src/v1/plugins/*, a VITE_DISTRIBUTION build arg (defaults to OSS behaviour when unset) and a Vite plugin that only inspects src/v1/plugins.

Database Migrations

  • None.

Env Config

  • VITE_DISTRIBUTION (build time, optional). Unset means OSS.

Relevant Docs

  • frontend/src/v1/README.md, section "Plugins".

Related Issues or PRs

Dependencies Versions

  • None.

Notes on Testing

  • bun run typecheck:v1, lint:v1, test:v1: clean; 20 vitest tests and 50 node tests (the scaffold's fixtures plus this PR's build-check and CLI tests).
  • The cloud overlay tests (unstract-cloud#1896) pass 7/7 against this branch. The check runs under Bun with no dependencies installed, as the cloud CI overlay step needs.
  • Frontend CI (typecheck, vitest, biome) runs once this PR targets main, i.e. after UN-4270 [FEAT] v1 scaffold, strict TypeScript and import boundary #2380 merges. The commands above were run locally.
  • The fixture tests cover the build check: duplicate route, two fillers on a single slot, a manifest for another distribution.

Screenshots

None: no UI.

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

jaseemjaskp and others added 11 commits October 26, 2026 17:51
…N-4270)

Adds frontend/src/v1/ as an isolated tree for the v1 frontend (P0-T01):

- Folder taxonomy from the spec, a public entry (src/v1/index.ts, imported
  by v0 as `@v1`) and a cloud overlay placeholder.
- src/v1/tsconfig.json: strict, no JS, unused locals/params are errors,
  `@v1` alias only. v0's tsconfig/jsconfig exclude src/v1.
- Biome overrides for the two-way import boundary and v1 bans (no `../`,
  `@/`, prop-types, antd, component CSS; noExplicitAny).
- scripts/check-v1.mjs for what Biome cannot express: TypeScript-only
  files, the folder taxonomy and import contract, PropTypes, `ant-`.
- typecheck:v1, lint:v1 and test:v1 scripts; v0 scripts are unchanged.
- test-fixtures/v1-boundary/ proves each rule fails and passes as
  specified, run with node --test so the v0 vitest suite never sees it.
- .gitignore re-includes src/v1/lib/ (the Python `lib/` rule matched it).

v0 build output, test counts, typecheck and Biome results are unchanged.
…270)

check-v1.mjs:
- Scan comments with a string/template-aware tokenizer; a `/*` or `//`
  inside a string or line comment no longer hides the imports after it.
- Read template-literal specifiers, import.meta.glob and `import(..., opts)`.
- Flag specifiers that leave src/v1 (`/`, `..`, `@/`, `src/`) itself, not
  only via Biome, which skips template literals.
- Check `@import`/`url()` and `ant-` in styles/ CSS; report symlinks;
  catch built `ant-` names; treat .test/.spec .mts/.cts as test files;
  ignore .DS_Store; drop the unused --root option.

biome.json: also ban `..`, `./../**`, `/**`, `antd/**`, `prop-types/**`.
tsconfig: skipLibCheck off so v1's own .d.ts files are checked.
.gitignore: re-include lib/ at any depth under src/v1.

Fixtures: a generated matrix over every folder-contract pair, every import
form, every outside spelling, a symlink case, and cases for each new rule;
tsc cases assert error codes; output matching excludes the command text and
spawn errors fail loudly. Comment and README corrections.
… (UN-4270)

- ci-frontend-lint: the typecheck job also runs `typecheck:v1` and
  `lint:v1`, so the strict v1 tsconfig and the scripts/check-v1.mjs checks
  (folder contract, TypeScript-only files, PropTypes, `ant-`) gate PRs now
  rather than from P0-T10.
- Pages may no longer import app/, test/ or the v1 entry, only the shared
  folders (components, shell, services, stores, hooks, lib, styles,
  plugins). Test files in a page may still import test/. Contract table,
  README and the fixture matrix updated together.
…(UN-4270)

check-v1.mjs: split the comment scanner into small helpers (cognitive
complexity 40 -> under 15), drop the nested ternary and nested template
literal, use String.raw for the import regexes, keep CLI flags in a Set.

Fixture runner:
- Cases and the expected folder contract are parsed text tables instead of
  repeated object literals (the 10% duplication).
- Sorts use an explicit localeCompare comparator.
- The contract matrix is built by a helper.
- Spawns /bin/sh by absolute path and no longer needs `git init` (Biome only
  needs the ignore file). The git check-ignore call keeps a PATH lookup, with
  the reason and a NOSONAR marker.

Fixture cases: re-exports rewritten as small functions and read-only props,
without changing what each case exercises.
…nnot see (UN-4270)

Addresses Greptile review on #2380:
- import.meta.glob([...]): every pattern in the array is checked, not only
  the first, so an allowed first pattern no longer hides a forbidden one.
- check-v1 --code also scans v0 (src/ outside v1, including the cloud
  src/plugins overlay) for template-literal imports, globs, require(),
  @/v1, src/v1, relative paths and CSS @import that reach into v1 other
  than through `@v1`. Biome still covers plain imports.

Fixtures: Greptile's example as a case, a glob array with a forbidden later
pattern, and a generated test of every v0 -> v1 form (denied and allowed).
The v0 scan reports nothing on the current tree.
v1 finds plugins at build time from src/v1/plugins/<id>/manifest.ts, which
the cloud overlay copies in. An absent plugin has no manifest, so its
slots show their fallbacks with no error; any other load or render error
is reported, and each slot has its own error boundary.

- lib/plugins: the typed slot contract, buildRegistry (distribution
  filter plus conflict checks for every distribution), <Slot> and
  pluginRoutes(). VITE_DISTRIBUTION picks oss, cloud or onprem.
- scripts/v1-plugins.mjs runs in every Vite build: a slot filled twice, a
  route defined twice or a plugin with no distribution fails the build,
  and the distribution matrix is published as plugin-matrix.json.
- v0's optional-plugin stub no longer applies under src/v1, where it
  turned a missing module into an empty object instead of an error.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The cloud workflows set it to cloud or onprem; the default, oss, keeps the
public image unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The cloud overlay copies plugins into frontend/src/v1/plugins/; ignore
them as frontend/src/plugins/* already is, keeping only the .gitkeep.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… lib through context

The scaffold allows lib/ to import only lib/, and only plugins/ may see the
overlaid manifests. plugins/index.ts now builds the registry and exports
PluginSlot and pluginRoutes(); lib/plugins keeps the contract and a Slot that
reads PluginSlot from PluginProvider, which app/ will mount. With no provider
every slot shows its fallback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…hai clash

TEMPORARY. With skipLibCheck off, @types/chai 5.2.3 and vitest 3.2.6 both
declare Chai containSubset, so typecheck:v1 fails once any v1 test imports
vitest. Drop this commit when #2380 settles it (pin @types/chai or turn the
option on).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comment thread frontend/scripts/v1-plugins.mjs Fixed
athul-rs and others added 3 commits October 28, 2026 12:11
…ts with esbuild

The build check now transpiles the registry and each manifest with esbuild,
so it runs on the Node versions the frontend supports. It also fails on a
folder with no manifest.ts, resolves argv[1] before the main-guard compare,
and has CLI tests. The README notes the distribution matrix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… the check before installing dependencies

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

via Greptile

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issue remains from this review.

Summary

Adds the v1 plugin contract, manifest discovery, slot rendering, and build-time checks.

  • V1 code can select plugins and render their named screen slots.
  • V1 builds check plugin manifests and publish a distribution matrix.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  M["Plugin manifests"] --> C["Build check"]
  C --> J["plugin-matrix.json"]
  M --> R["Registry for selected distribution"]
  R --> P["PluginProvider"]
  P --> S["Slot"]
  S --> F["Lazy filler"]
  S --> B["Fallback when absent or failed"]
Loading

Reviews (4) · Last reviewed commit: "UN-4286 [MISC] Merge main after the v1 s..." · Reviewed by Greptile

Comment thread frontend/scripts/v1-plugins.mjs Resolved
jaseemjaskp and others added 5 commits October 29, 2026 10:38
…ins in the ignore check (UN-4270)

Reported by Athul while building #2394/#2395 on this branch:
- src/v1/tsconfig.json: skipLibCheck back on, as in tsconfig.json. Off, tsc
  also checked third-party typings, and @types/chai 5.2.3 and vitest 3.2.6
  both declare `containSubset`, so any v1 test importing vitest failed
  typecheck:v1. A fixture test now imports vitest to keep this covered.
- The "no v1 file is git-ignored" check skips src/v1/plugins/<id>/: cloud
  plugins copied there are git-ignored on purpose (#2394).
…and test slot recovery in place

- v1-plugins.mjs: with no --dir, `flag + 1` was 0, so the first argument
  (the output file) was skipped and the matrix went to stdout. A fixture
  now covers the file argument with and without --dir.
- Slot.test.tsx: the "recovers in place" test wrapped the slot in a <div>,
  which remounted it, so the boundary's resetKey reset never ran. It now
  re-renders the same slot; with resetKey={id} the test fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
From Ali's, Athul's and Vishnu's reviews on #2380, each reproduced first:

- tsconfig/jsconfig map `@v1/*` too: once v0 imports the entry, tsc follows
  it into src/v1, where cross-folder `@v1/...` imports must resolve
  (would break `bun run typecheck` at UN-4271). Lint keeps v0 on `@v1`.
- check-v1 scanner tracks regex literals, so a `/*` inside one no longer
  hides the rest of the file; CSS blanks only block comments (`//` is URL).
- Imports are read by argument: literals, templates by their fixed prefix,
  every glob pattern, and new URL(..., import.meta.url). In v1 an import the
  guard cannot read fails closed; a computed v1 path is allowed only in
  folders that may import everything (the lazy route registry in app/).
- v0 specifiers are normalized before the check (/src/v1, @/./v1, ...).
- v1 styles may not @import antd packages.
- Biome's v0 rule matches only alias spellings of src/v1, so `uuid/v1` or
  `@/api/v1` no longer fail; relative spellings are check-v1's (in CI).
- CI runs `test:v1` (fixtures) on Node 22; setup-biome pinned to a SHA
  (SonarCloud S7637).

Fixtures assert the exact message and count per form, cover each fix, and
check that comments, JSX and look-alike class names stay quiet. Against the
previous guard, 9 of the new tests fail.
Static files (icons, logos, images) get one home, src/v1/assets/, imported
as @v1/assets/... (e.g. logo.svg?react). v1 cannot reach v0's src/assets/.
Raised by Ali on #2380: Lane I's P1-T22 needs SVG icons and provider logos,
and a .svg outside the taxonomy failed --files.

components/ui, components/patterns, shell, pages and styles may import
assets/; assets/ imports nothing but itself (room for a generated icon map).
README, contract table and the fixture matrix updated together;
v1-allowed-imports imports an SVG as a React component under typecheck:v1.
…s (UN-4270)

Greptile on #2380: `export const example = "import(name)";` failed lint:v1,
because the call matcher also ran over string contents. Static import text
in a string ('import x from "@/helpers/x"') and in templates or regexes was
read as an import too.

The comment scanner now also returns a code-only view (string, template
and regex bodies blanked, offsets kept). Statements and calls are located
there; the specifier is read from the real text at the same offset. The
import-form test gains a quiet case with import text in a string, a
template and a regex; it fails against the previous guard.
greptile-apps[bot]
greptile-apps Bot previously approved these changes Oct 29, 2026
…r (UN-4270)

Greptile P1 on #2380, introduced by 3889a34: the code-only view blanked
string bodies, and the apostrophe in JSX text (`It's`, `{name}'s`) opened a
"string" that ran to the end of the line, hiding a forbidden import after
it.

A quote now starts a string only in expression position (after an operator,
`(`, `,`, `=`, `[`, `{` or a keyword such as return/case/import/from) and only
if it closes on its line, as JS strings must; otherwise it is JSX text. The
regex-literal check shares the same token test.

Fixtures: Greptile's example and the `{name}'s` form each must report the
import; a JSX attribute and quoted JSX text stay quiet. A differential run
over the 409 real v0 files finds no real import hidden by the code-only view.
athul-rs and others added 2 commits October 29, 2026 20:18
…6-plugin-manifest

# Conflicts:
#	frontend/src/v1/tsconfig.json
…n read

The scaffold now rejects dynamic imports it cannot analyse. A template
whose fixed prefix names the folder still fails at run time, as the test
needs, and passes the guard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@greptile-apps
greptile-apps Bot dismissed their stale review October 29, 2026 14:56

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

…okenizer (UN-4270)

The tokenizer guessed regex vs division and string vs JSX text from the
previous token. JSX text such as `{done}/{total}`, `files/*.ts`, a stray
backtick, a multi-line attribute or `range.from / x` flipped it into a state
that never closed, and check-v1 then passed the rest of the file.

check-v1 now parses with @babel/parser (TypeScript 7 has no JS parser API)
and walks import/export statements, import(), require(), import.meta.glob,
new URL(..., import.meta.url), `typeof import()` and `import x = require()`.
Escapes are decoded and methods named import/require are not imports.

Also:
- v1 rejects concatenated specifiers, template prefixes with no `/`, a
  non-literal new URL and files that do not parse; v0 reports a template or
  glob prefix that could grow into src/v1.
- v1 styles: check @reference/@plugin/@config and the folder contract.
- Biome: relative v0 -> v1 spellings are back in the v0 group.
- A `../` that stays inside v1 is reported as such, not as "outside".
- v0 symlinks into src/v1 are reported; file symlinks are scanned.
- README: name what Biome covers; asset placement is convention.
- Fixtures for every reported repro and the remaining #5 branches; drop the
  unused src/v1/plugins/<id> ignore filter.
…s (UN-4270)

`import("@v1/x" as const)` wrapped the specifier in a TSAsExpression, so
v0 treated it as opaque and skipped it. Unwrap as/satisfies/<T>x/x! first.
Also share one helper between the outside and climbs tests.
- import.meta.glob: resolve relative patterns against the `base` option; a
  base that is not a string literal is unanalysable in v1.
- See through TypeScript wrappers on the import markers too
  (`import.meta.url as string`, `(import.meta.glob as any)()`,
  `(require as ...)()`, `new (URL as ...)()`, a glob array `as const`).
- v0: judge a `+` chain or `.concat()` by the string it starts with, as
  Vite globs it, and a conditional by both branches.
- Report a `..` after the first wildcard or `${}`, in v1 and v0.
- Read stylesheets with PostCSS: case-insensitive at-rules, `@import"x"`
  with no space, and `/*` inside strings.
- Root tsconfig includes vite-env.d.ts, so v1 assets, CSS and
  import.meta.env reached through `@v1` typecheck in the v0 lane.
- Parse .d.ts files in declaration mode.
- Split nodes() and checkV0() into helpers (Sonar S3776).
…file types (UN-4270)

- CSS values are read with postcss-value-parser: every url() and every
  string inside image-set() / -webkit-image-set(), with backslash escapes
  removed as Vite does. @import targets use the same parser; an unreadable
  one is unanalysable.
- v1 holds only code, styles (in styles/), Vite asset types, JSON, Markdown
  and .gitkeep; --files reports any other type. STYLE_EXT and the Biome
  component-style group cover all of Vite's CSS languages.
- --code reads every non-asset file in v1 and v0, parsing an unknown type
  as code (Vite can load any file with ?worker), and checks style files
  wherever they are, so --code alone catches what --files rejects.
The cloud build copies its v1 plugins into src/v1/plugins/<id>/, as it
does v0 plugins into src/plugins/. Ignore them, keeping only the public
placeholder manifest.ts tracked, so cloud code cannot be committed here.

The ignore-check test skips a local copy of those plugins, and a new test
asserts they are ignored while manifest.ts is not.
jaseemjaskp added a commit that referenced this pull request Nov 2, 2026
)

* feat(frontend): v1 scaffold, strict TypeScript and import boundary (UN-4270)

Adds frontend/src/v1/ as an isolated tree for the v1 frontend (P0-T01):

- Folder taxonomy from the spec, a public entry (src/v1/index.ts, imported
  by v0 as `@v1`) and a cloud overlay placeholder.
- src/v1/tsconfig.json: strict, no JS, unused locals/params are errors,
  `@v1` alias only. v0's tsconfig/jsconfig exclude src/v1.
- Biome overrides for the two-way import boundary and v1 bans (no `../`,
  `@/`, prop-types, antd, component CSS; noExplicitAny).
- scripts/check-v1.mjs for what Biome cannot express: TypeScript-only
  files, the folder taxonomy and import contract, PropTypes, `ant-`.
- typecheck:v1, lint:v1 and test:v1 scripts; v0 scripts are unchanged.
- test-fixtures/v1-boundary/ proves each rule fails and passes as
  specified, run with node --test so the v0 vitest suite never sees it.
- .gitignore re-includes src/v1/lib/ (the Python `lib/` rule matched it).

v0 build output, test counts, typecheck and Biome results are unchanged.

* fix(frontend): close v1 guardrail bypasses found in self-review (UN-4270)

check-v1.mjs:
- Scan comments with a string/template-aware tokenizer; a `/*` or `//`
  inside a string or line comment no longer hides the imports after it.
- Read template-literal specifiers, import.meta.glob and `import(..., opts)`.
- Flag specifiers that leave src/v1 (`/`, `..`, `@/`, `src/`) itself, not
  only via Biome, which skips template literals.
- Check `@import`/`url()` and `ant-` in styles/ CSS; report symlinks;
  catch built `ant-` names; treat .test/.spec .mts/.cts as test files;
  ignore .DS_Store; drop the unused --root option.

biome.json: also ban `..`, `./../**`, `/**`, `antd/**`, `prop-types/**`.
tsconfig: skipLibCheck off so v1's own .d.ts files are checked.
.gitignore: re-include lib/ at any depth under src/v1.

Fixtures: a generated matrix over every folder-contract pair, every import
form, every outside spelling, a symlink case, and cases for each new rule;
tsc cases assert error codes; output matching excludes the command text and
spawn errors fail loudly. Comment and README corrections.

* feat(frontend): run v1 checks in CI and tighten what pages may import (UN-4270)

- ci-frontend-lint: the typecheck job also runs `typecheck:v1` and
  `lint:v1`, so the strict v1 tsconfig and the scripts/check-v1.mjs checks
  (folder contract, TypeScript-only files, PropTypes, `ant-`) gate PRs now
  rather than from P0-T10.
- Pages may no longer import app/, test/ or the v1 entry, only the shared
  folders (components, shell, services, stores, hooks, lib, styles,
  plugins). Test files in a page may still import test/. Contract table,
  README and the fixture matrix updated together.

* refactor(frontend): address SonarCloud findings on the v1 guardrails (UN-4270)

check-v1.mjs: split the comment scanner into small helpers (cognitive
complexity 40 -> under 15), drop the nested ternary and nested template
literal, use String.raw for the import regexes, keep CLI flags in a Set.

Fixture runner:
- Cases and the expected folder contract are parsed text tables instead of
  repeated object literals (the 10% duplication).
- Sorts use an explicit localeCompare comparator.
- The contract matrix is built by a helper.
- Spawns /bin/sh by absolute path and no longer needs `git init` (Biome only
  needs the ignore file). The git check-ignore call keeps a PATH lookup, with
  the reason and a NOSONAR marker.

Fixture cases: re-exports rewritten as small functions and read-only props,
without changing what each case exercises.

* refactor(frontend): split the contract-matrix builder to cut its complexity (UN-4270)

* fix(frontend): check every glob pattern and v0 -> v1 imports Biome cannot see (UN-4270)

Addresses Greptile review on #2380:
- import.meta.glob([...]): every pattern in the array is checked, not only
  the first, so an allowed first pattern no longer hides a forbidden one.
- check-v1 --code also scans v0 (src/ outside v1, including the cloud
  src/plugins overlay) for template-literal imports, globs, require(),
  @/v1, src/v1, relative paths and CSS @import that reach into v1 other
  than through `@v1`. Biome still covers plain imports.

Fixtures: Greptile's example as a case, a glob array with a forbidden later
pattern, and a generated test of every v0 -> v1 form (denied and allowed).
The v0 scan reports nothing on the current tree.

* fix(frontend): typecheck v1 tests that import vitest; skip cloud plugins in the ignore check (UN-4270)

Reported by Athul while building #2394/#2395 on this branch:
- src/v1/tsconfig.json: skipLibCheck back on, as in tsconfig.json. Off, tsc
  also checked third-party typings, and @types/chai 5.2.3 and vitest 3.2.6
  both declare `containSubset`, so any v1 test importing vitest failed
  typecheck:v1. A fixture test now imports vitest to keep this covered.
- The "no v1 file is git-ignored" check skips src/v1/plugins/<id>/: cloud
  plugins copied there are git-ignored on purpose (#2394).

* fix(frontend): close v1 guardrail gaps from team review (UN-4270)

From Ali's, Athul's and Vishnu's reviews on #2380, each reproduced first:

- tsconfig/jsconfig map `@v1/*` too: once v0 imports the entry, tsc follows
  it into src/v1, where cross-folder `@v1/...` imports must resolve
  (would break `bun run typecheck` at UN-4271). Lint keeps v0 on `@v1`.
- check-v1 scanner tracks regex literals, so a `/*` inside one no longer
  hides the rest of the file; CSS blanks only block comments (`//` is URL).
- Imports are read by argument: literals, templates by their fixed prefix,
  every glob pattern, and new URL(..., import.meta.url). In v1 an import the
  guard cannot read fails closed; a computed v1 path is allowed only in
  folders that may import everything (the lazy route registry in app/).
- v0 specifiers are normalized before the check (/src/v1, @/./v1, ...).
- v1 styles may not @import antd packages.
- Biome's v0 rule matches only alias spellings of src/v1, so `uuid/v1` or
  `@/api/v1` no longer fail; relative spellings are check-v1's (in CI).
- CI runs `test:v1` (fixtures) on Node 22; setup-biome pinned to a SHA
  (SonarCloud S7637).

Fixtures assert the exact message and count per form, cover each fix, and
check that comments, JSX and look-alike class names stay quiet. Against the
previous guard, 9 of the new tests fail.

* feat(frontend): add an assets/ folder to the v1 taxonomy (UN-4270)

Static files (icons, logos, images) get one home, src/v1/assets/, imported
as @v1/assets/... (e.g. logo.svg?react). v1 cannot reach v0's src/assets/.
Raised by Ali on #2380: Lane I's P1-T22 needs SVG icons and provider logos,
and a .svg outside the taxonomy failed --files.

components/ui, components/patterns, shell, pages and styles may import
assets/; assets/ imports nothing but itself (room for a generated icon map).
README, contract table and the fixture matrix updated together;
v1-allowed-imports imports an SVG as a React component under typecheck:v1.

* fix(frontend): find v1 imports only in executable code, not in strings (UN-4270)

Greptile on #2380: `export const example = "import(name)";` failed lint:v1,
because the call matcher also ran over string contents. Static import text
in a string ('import x from "@/helpers/x"') and in templates or regexes was
read as an import too.

The comment scanner now also returns a code-only view (string, template
and regex bodies blanked, offsets kept). Statements and calls are located
there; the specifier is read from the real text at the same offset. The
import-form test gains a quiet case with import text in a string, a
template and a regex; it fails against the previous guard.

* fix(frontend): don't read JSX apostrophes as strings in the v1 scanner (UN-4270)

Greptile P1 on #2380, introduced by 3889a34: the code-only view blanked
string bodies, and the apostrophe in JSX text (`It's`, `{name}'s`) opened a
"string" that ran to the end of the line, hiding a forbidden import after
it.

A quote now starts a string only in expression position (after an operator,
`(`, `,`, `=`, `[`, `{` or a keyword such as return/case/import/from) and only
if it closes on its line, as JS strings must; otherwise it is JSX text. The
regex-literal check shares the same token test.

Fixtures: Greptile's example and the `{name}'s` form each must report the
import; a JSX attribute and quoted JSX text stay quiet. A differential run
over the 409 real v0 files finds no real import hidden by the code-only view.

* fix(frontend): read v1 imports from a Babel AST, not a hand-written tokenizer (UN-4270)

The tokenizer guessed regex vs division and string vs JSX text from the
previous token. JSX text such as `{done}/{total}`, `files/*.ts`, a stray
backtick, a multi-line attribute or `range.from / x` flipped it into a state
that never closed, and check-v1 then passed the rest of the file.

check-v1 now parses with @babel/parser (TypeScript 7 has no JS parser API)
and walks import/export statements, import(), require(), import.meta.glob,
new URL(..., import.meta.url), `typeof import()` and `import x = require()`.
Escapes are decoded and methods named import/require are not imports.

Also:
- v1 rejects concatenated specifiers, template prefixes with no `/`, a
  non-literal new URL and files that do not parse; v0 reports a template or
  glob prefix that could grow into src/v1.
- v1 styles: check @reference/@plugin/@config and the folder contract.
- Biome: relative v0 -> v1 spellings are back in the v0 group.
- A `../` that stays inside v1 is reported as such, not as "outside".
- v0 symlinks into src/v1 are reported; file symlinks are scanned.
- README: name what Biome covers; asset placement is convention.
- Fixtures for every reported repro and the remaining #5 branches; drop the
  unused src/v1/plugins/<id> ignore filter.

* fix(frontend): read v1 import specifiers through TypeScript assertions (UN-4270)

`import("@v1/x" as const)` wrapped the specifier in a TSAsExpression, so
v0 treated it as opaque and skipped it. Unwrap as/satisfies/<T>x/x! first.
Also share one helper between the outside and climbs tests.

* fix(frontend): close v1 boundary gaps from the daa1a18 review (UN-4270)

- import.meta.glob: resolve relative patterns against the `base` option; a
  base that is not a string literal is unanalysable in v1.
- See through TypeScript wrappers on the import markers too
  (`import.meta.url as string`, `(import.meta.glob as any)()`,
  `(require as ...)()`, `new (URL as ...)()`, a glob array `as const`).
- v0: judge a `+` chain or `.concat()` by the string it starts with, as
  Vite globs it, and a conditional by both branches.
- Report a `..` after the first wildcard or `${}`, in v1 and v0.
- Read stylesheets with PostCSS: case-insensitive at-rules, `@import"x"`
  with no space, and `/*` inside strings.
- Root tsconfig includes vite-env.d.ts, so v1 assets, CSS and
  import.meta.env reached through `@v1` typecheck in the v0 lane.
- Parse .d.ts files in declaration mode.
- Split nodes() and checkV0() into helpers (Sonar S3776).

* fix(frontend): read CSS image-set() and escaped url()s; allowlist v1 file types (UN-4270)

- CSS values are read with postcss-value-parser: every url() and every
  string inside image-set() / -webkit-image-set(), with backslash escapes
  removed as Vite does. @import targets use the same parser; an unreadable
  one is unanalysable.
- v1 holds only code, styles (in styles/), Vite asset types, JSON, Markdown
  and .gitkeep; --files reports any other type. STYLE_EXT and the Biome
  component-style group cover all of Vite's CSS languages.
- --code reads every non-asset file in v1 and v0, parsing an unknown type
  as code (Vite can load any file with ?worker), and checks style files
  wherever they are, so --code alone catches what --files rejects.

* chore: git-ignore cloud v1 plugins copied into src/v1/plugins/ (UN-4270)

The cloud build copies its v1 plugins into src/v1/plugins/<id>/, as it
does v0 plugins into src/plugins/. Ignore them, keeping only the public
placeholder manifest.ts tracked, so cloud code cannot be committed here.

The ignore-check test skips a local copy of those plugins, and a new test
asserts they are ignored while manifest.ts is not.
Base automatically changed from UN-4270-v1-scaffold to main November 2, 2026 09:09
athul-rs and others added 2 commits November 3, 2026 18:50
Keep the plugin loader layout (plugins/index.ts + .gitkeep) in .gitignore
and point the scaffold's ignore and symlink fixtures at it, since this
branch replaced the placeholder plugins/manifest.ts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's copies of the scaffold files equal the scaffold head merged in the
previous commit, so the conflicts keep this branch's side. The placeholder
plugins/manifest.ts that main reintroduces stays removed: plugins/index.ts
replaces it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

✅ All checks passed! No linting or formatting issues found.

@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
⚪ e2e-agent-kv e2e 0 0 0 1 0.1
✅ e2e-api-deployment e2e 3 0 0 0 16.3
✅ e2e-coowners e2e 1 0 0 0 1.3
✅ e2e-etl e2e 1 0 0 0 16.6
✅ e2e-login e2e 2 0 0 0 1.3
✅ e2e-prompt-studio e2e 1 0 0 0 9.6
✅ e2e-smoke e2e 2 0 0 0 1.2
✅ e2e-workflow e2e 1 0 0 0 12.4
✅ frontend unit 674 0 0 0 15.7
✅ integration-backend integration 1459 0 0 26 58.2
✅ integration-connectors integration 1 0 0 7 7.0
✅ integration-workers integration 181 0 0 1 40.0
❌ ui e2e 0 1 0 0 0.0
✅ unit-agent-kv-schema unit 143 0 0 0 1.1
✅ unit-backend unit 2137 0 0 1 38.7
✅ unit-connectors unit 82 0 0 0 8.2
✅ unit-core unit 281 0 0 0 2.0
✅ unit-filesystem unit 1 0 0 0 2.6
✅ unit-platform-service unit 18 0 0 0 1.7
✅ unit-rig unit 120 0 0 0 2.9
✅ unit-runner unit 12 0 0 0 1.8
✅ unit-sdk1 unit 1197 0 0 0 36.0
✅ unit-tool-sidecar unit 8 0 0 0 1.2
✅ unit-workers unit 1647 0 0 2 104.2
TOTAL 7972 1 0 38 380.1

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • api-deployment-run — covered by e2e-api-deployment
  • mcp-server-auth — covered by integration-backend
  • mcp-platform-auth — covered by integration-backend
  • platform-key-whoami — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • connector-register-test — covered by integration-backend
  • pipeline-etl-execute — covered by e2e-etl
  • usage-aggregate-read — covered by integration-backend
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants